package com.example.clock_demo.service.impl;

import com.alibaba.excel.EasyExcel;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.example.clock_demo.dto.*;
import com.example.clock_demo.entitys.PunchRecord;
import com.example.clock_demo.entitys.Scheduling;
import com.example.clock_demo.entitys.Shift;
import com.example.clock_demo.handler.MonthlyAttendanceExcelHandler;
import com.example.clock_demo.handler.MonthlyAttendanceSummaryExcelHandler;
import com.example.clock_demo.mapper.SchedulingMapper;
import com.example.clock_demo.service.AttendanceService;
import com.example.clock_demo.service.PunchRecordService;
import com.example.clock_demo.service.SchedulingService;
import com.example.clock_demo.service.ShiftService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.net.URLEncoder;
import java.time.Duration;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.YearMonth; // 导入 YearMonth
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.stream.Collectors;
@Service
public class AttendanceServiceImpl implements AttendanceService {

    @Autowired
    private ShiftService shiftService;
    @Autowired
    private SchedulingService schedulingService;
    @Autowired
    private PunchRecordService punchRecordService;



    private static final int EARLY_PUNCH_IN_MARGIN_MINUTES = 60; // 默认提前打卡时间（分钟）
    private static final int LATE_PUNCH_OUT_MARGIN_MINUTES = 60; // 默认延迟打卡时间（分钟）
    private static final int ATTENDANCE_GRACE_MINUTES = 0; // 上下班迟到早退宽限分钟数

    /**
     * 获取指定条件和月份的月度考勤报告，支持分页。
     * @param yearMonth 月份 (YearMonth 对象)
     * @param teamName 班组名称 (模糊查询，可选)
     * @param employeeName 姓名 (模糊查询，可选)
     * @param pageNum 当前页码
     * @param pageSize 每页记录数
     * @return 分页的月度考勤报告列表
     */
    @Override
    public Page<AttendanceMonthlyReportDTO> getMonthlyAttendanceReports(
            YearMonth yearMonth, String teamName, String employeeName, long pageNum, long pageSize) {

        // 1. 构建查询条件
        LocalDate queryMonthStart = yearMonth.atDay(1);
        LocalDate queryMonthEnd = yearMonth.atEndOfMonth();

        QueryWrapper<Scheduling> employeeQueryWrapper = new QueryWrapper<>();
        employeeQueryWrapper.select("DISTINCT employee_number") // 只查询不重复的员工工号
                .between("schedule_date", queryMonthStart, queryMonthEnd);
        if (teamName != null && !teamName.isEmpty()) {
            employeeQueryWrapper.like("team_name", teamName);
        }
        if (employeeName != null && !employeeName.isEmpty()) {
            employeeQueryWrapper.like("employee_name", employeeName);
        }
        employeeQueryWrapper.orderByAsc("employee_number"); // 排序保证分页稳定

        // 2. 直接使用 MyBatis-Plus 分页查询
        Page<Scheduling> schedulingPage = new Page<>(pageNum, pageSize);
        Page<Scheduling> pagedSchedulings = schedulingService.page(schedulingPage, employeeQueryWrapper);

        // 如果没有找到符合条件的员工，直接返回空分页结果
        if (pagedSchedulings.getRecords().isEmpty()) {
            return new Page<>(pageNum, pageSize, 0);
        }

        // 3. 提取员工工号（由于使用了 DISTINCT，每个员工只会有一条记录）
        List<String> pagedEmployeeNumbers = pagedSchedulings.getRecords().stream()
                .map(Scheduling::getEmployeeNumber)
                .collect(Collectors.toList());

        // 4. 批量获取当前页所有员工的基础数据
        BatchEmployeeDataDTO batchData = batchGetEmployeeData(pagedEmployeeNumbers, yearMonth);

        // 5. 为当前页的每个员工生成月度考勤报告
        List<AttendanceMonthlyReportDTO> pagedReports = new ArrayList<>();
        for (String empNumber : pagedEmployeeNumbers) {
            AttendanceMonthlyReportDTO report = getMonthlyAttendanceReportForSingleEmployeeOptimized(
                    empNumber, yearMonth, batchData);
            pagedReports.add(report);
        }

        // 6. 构建并返回分页结果
        Page<AttendanceMonthlyReportDTO> resultPage = new Page<>(pageNum, pageSize, pagedSchedulings.getTotal());
        resultPage.setRecords(pagedReports);
        resultPage.setTotal(pagedSchedulings.getTotal());

        return resultPage;
    }


    /**
     * 批量获取员工基础数据的DTO
     */
    private static class BatchEmployeeDataDTO {
        private Map<String, Shift> shiftMap;
        private Map<String, List<Scheduling>> employeeSchedulingMap;
        private Map<String, List<PunchRecord>> employeePunchRecordsMap;

        public BatchEmployeeDataDTO(Map<String, Shift> shiftMap,
                                    Map<String, List<Scheduling>> employeeSchedulingMap,
                                    Map<String, List<PunchRecord>> employeePunchRecordsMap) {
            this.shiftMap = shiftMap;
            this.employeeSchedulingMap = employeeSchedulingMap;
            this.employeePunchRecordsMap = employeePunchRecordsMap;
        }

        // getters
        public Map<String, Shift> getShiftMap() { return shiftMap; }
        public Map<String, List<Scheduling>> getEmployeeSchedulingMap() { return employeeSchedulingMap; }
        public Map<String, List<PunchRecord>> getEmployeePunchRecordsMap() { return employeePunchRecordsMap; }
    }

    /**
     * 批量获取员工基础数据
     */
    private BatchEmployeeDataDTO batchGetEmployeeData(List<String> employeeNumbers, YearMonth yearMonth) {
        LocalDate startDate = yearMonth.atDay(1);
        LocalDate endDate = yearMonth.atEndOfMonth();

        // 1. 获取所有班次信息
        Map<String, Shift> shiftMap = shiftService.list().stream()
                .collect(Collectors.toMap(Shift::getShiftCode, shift -> shift));

        // 2. 批量获取所有员工的排班记录
        QueryWrapper<Scheduling> schedulingQueryWrapper = new QueryWrapper<>();
        schedulingQueryWrapper.in("employee_number", employeeNumbers)
                .between("schedule_date", startDate, endDate)
                .orderByAsc("schedule_date");

        List<Scheduling> allSchedulings = schedulingService.list(schedulingQueryWrapper);
        Map<String, List<Scheduling>> employeeSchedulingMap = allSchedulings.stream()
                .collect(Collectors.groupingBy(Scheduling::getEmployeeNumber));

        // 3. 批量获取所有员工的打卡记录
        QueryWrapper<PunchRecord> punchQueryWrapper = new QueryWrapper<>();
        punchQueryWrapper.in("employee_number", employeeNumbers)
                .between("punch_time", startDate.minusDays(1).atStartOfDay(), endDate.plusDays(1).atTime(LocalTime.MAX))
                .eq("is_valid", true)
                .orderByAsc("punch_time");

        List<PunchRecord> allPunchRecords = punchRecordService.list(punchQueryWrapper);
        Map<String, List<PunchRecord>> employeePunchRecordsMap = allPunchRecords.stream()
                .collect(Collectors.groupingBy(PunchRecord::getEmployeeNumber));

        return new BatchEmployeeDataDTO(shiftMap, employeeSchedulingMap, employeePunchRecordsMap);
    }

    /**
     * 优化后的单个员工月度考勤报告生成方法
     */
    private AttendanceMonthlyReportDTO getMonthlyAttendanceReportForSingleEmployeeOptimized(
            String employeeNumber, YearMonth yearMonth, BatchEmployeeDataDTO batchData) {

        // 1. 初始化数据结构
        AttendanceMonthlyReportDTO report = new AttendanceMonthlyReportDTO();
        List<AttendanceDailyRecordDTO> dailyRecords = new ArrayList<>();
        AttendanceMonthlySummaryDTO summary = new AttendanceMonthlySummaryDTO();
        summary.setEmployeeNumber(employeeNumber);

        LocalDate startDate = yearMonth.atDay(1);
        LocalDate endDate = yearMonth.atEndOfMonth();
        summary.setTotalDaysInMonth(yearMonth.lengthOfMonth());

        // 2. 从批量数据中获取该员工的数据
        Map<String, Shift> shiftMap = batchData.getShiftMap();
        List<Scheduling> schedulings = batchData.getEmployeeSchedulingMap().getOrDefault(employeeNumber, new ArrayList<>());
        List<PunchRecord> allRelevantPunchRecords = batchData.getEmployeePunchRecordsMap().getOrDefault(employeeNumber, new ArrayList<>());

        // 将排班记录按日期分组
        Map<LocalDate, Scheduling> schedulingMap = schedulings.stream()
                .collect(Collectors.toMap(Scheduling::getScheduleDate, scheduling -> scheduling));

        // 将打卡记录按自然日分组
        Map<LocalDate, List<PunchRecord>> punchRecordsMap = allRelevantPunchRecords.stream()
                .collect(Collectors.groupingBy(punchRecord -> punchRecord.getPunchTime().toLocalDate()));

        // 3. 统计变量
        int whiteShiftDays = 0;
        int nightShiftDays = 0;
        int actualAttendanceDays = 0;
        int lateCount = 0;
        int earlyLeaveCount = 0;
        BigDecimal totalScheduledHours = BigDecimal.ZERO;
        BigDecimal totalActualWorkHours = BigDecimal.ZERO;

        // 4. 逐天计算考勤明细
        for (LocalDate currentDate = startDate; !currentDate.isAfter(endDate); currentDate = currentDate.plusDays(1)) {
            AttendanceDailyRecordDTO dailyRecord = new AttendanceDailyRecordDTO();
            dailyRecord.setDate(currentDate);
            dailyRecord.setDayOfWeek(currentDate.getDayOfWeek().name());
            dailyRecord.setEmployeeNumber(employeeNumber);

            // 设置员工基本信息
            if (!schedulings.isEmpty()) {
                dailyRecord.setTeamName(schedulings.get(0).getTeamName());
                dailyRecord.setEmployeeName(schedulings.get(0).getEmployeeName());
            } else if (!allRelevantPunchRecords.isEmpty()) {
                dailyRecord.setTeamName(allRelevantPunchRecords.get(0).getTeamName());
                dailyRecord.setEmployeeName(allRelevantPunchRecords.get(0).getEmployeeName());
            } else {
                dailyRecord.setTeamName("N/A");
                dailyRecord.setEmployeeName("N/A");
            }

            Scheduling scheduling = schedulingMap.get(currentDate);

            // 检查是否有排班
            if (scheduling != null) {
                Shift shift = shiftMap.get(scheduling.getShiftCode());

                dailyRecord.setShiftCode(scheduling.getShiftCode());
                dailyRecord.setScheduledDurationHours(scheduling.getScheduledDurationHours() != null ?
                        scheduling.getScheduledDurationHours() : BigDecimal.ZERO);

                if (shift != null) {
                    dailyRecord.setShiftName(shift.getShiftName());
                    dailyRecord.setScheduledStartTime(shift.getStartTime());
                    dailyRecord.setScheduledEndTime(shift.getEndTime());

                    // 核心逻辑：区分工作班次和休息班次
                    if ("R".equals(shift.getShiftCode())) {
                        // 休息班次
                        dailyRecord.setAttendanceStatus("休息日");
                        dailyRecord.setActualWorkHours(BigDecimal.ZERO);
                        dailyRecord.setIsLate(false);
                        dailyRecord.setIsEarlyLeave(false);
                        dailyRecord.setIsAbsent(false);
                    } else if (shift.getStartTime() != null && shift.getEndTime() != null) {
                        // 工作班次
                        summary.setTotalScheduledDays(summary.getTotalScheduledDays() + 1);
                        totalScheduledHours = totalScheduledHours.add(dailyRecord.getScheduledDurationHours());

                        // 统计白班/夜班天数
                        if (Arrays.asList("LD", "D", "E", "M").contains(shift.getShiftCode())) {
                            whiteShiftDays++;
                        } else if (Arrays.asList("LN", "N").contains(shift.getShiftCode())) {
                            nightShiftDays++;
                        }

                        // 查找打卡记录
                        Map<String, PunchRecord> punches = findPunchesForShift(currentDate, shift, punchRecordsMap);
                        PunchRecord firstPunch = punches.get("first");
                        PunchRecord lastPunch = punches.get("last");

                        if (firstPunch != null && lastPunch != null) {
                            dailyRecord.setActualPunchInTime(firstPunch.getPunchTime().toLocalTime());
                            dailyRecord.setActualPunchOutTime(lastPunch.getPunchTime().toLocalTime());
                            dailyRecord.setPunchInDevice(firstPunch.getPunchDevice());
                            dailyRecord.setPunchOutDevice(lastPunch.getPunchDevice());

                            // 计算工时和迟到早退...
                            Duration actualDuration = Duration.between(firstPunch.getPunchTime(), lastPunch.getPunchTime());
                            dailyRecord.setActualWorkHours(BigDecimal.valueOf(actualDuration.toMinutes())
                                    .divide(BigDecimal.valueOf(60), 2, RoundingMode.HALF_UP));
                            totalActualWorkHours = totalActualWorkHours.add(dailyRecord.getActualWorkHours());
                            actualAttendanceDays++;

                            boolean isLate = false;
                            LocalDateTime plannedShiftStartDateTime = LocalDateTime.of(currentDate, shift.getStartTime());
                            LocalDateTime lateToleranceTime = plannedShiftStartDateTime.plusMinutes(ATTENDANCE_GRACE_MINUTES);
                            if (firstPunch.getPunchTime().isAfter(lateToleranceTime)) {
                                isLate = true;
                                lateCount++;
                            }

                            boolean isEarlyLeave = false;
                            LocalDateTime plannedShiftEndDateTime;
                            if (shift.getStartTime().isAfter(shift.getEndTime())) {
                                plannedShiftEndDateTime = LocalDateTime.of(currentDate.plusDays(1), shift.getEndTime());
                            } else {
                                plannedShiftEndDateTime = LocalDateTime.of(currentDate, shift.getEndTime());
                            }
                            LocalDateTime earlyLeaveToleranceTime = plannedShiftEndDateTime.minusMinutes(ATTENDANCE_GRACE_MINUTES);
                            if (lastPunch.getPunchTime().isBefore(earlyLeaveToleranceTime)) {
                                isEarlyLeave = true;
                                earlyLeaveCount++;
                            }

                            dailyRecord.setIsLate(isLate);
                            dailyRecord.setIsEarlyLeave(isEarlyLeave);
                            dailyRecord.setIsAbsent(false); // 不缺勤

                            if (isLate && isEarlyLeave) {
                                dailyRecord.setAttendanceStatus("迟到早退");
                            } else if (isLate) {
                                dailyRecord.setAttendanceStatus("迟到");
                            } else if (isEarlyLeave) {
                                dailyRecord.setAttendanceStatus("早退");
                            } else {
                                dailyRecord.setAttendanceStatus("正常");
                            }

                        } else {

                            // 1. 统一将最终结果设置为“缺勤”
                            dailyRecord.setAttendanceStatus("缺勤");
                            dailyRecord.setIsAbsent(true);
                            dailyRecord.setActualWorkHours(BigDecimal.ZERO);
                            dailyRecord.setIsLate(false);
                            dailyRecord.setIsEarlyLeave(false);

                            // 2.保留实际发生的打卡记录
                            if (firstPunch != null) {
                                dailyRecord.setActualPunchInTime(firstPunch.getPunchTime().toLocalTime());
                                dailyRecord.setPunchInDevice(firstPunch.getPunchDevice());
                            } else {
                                dailyRecord.setActualPunchInTime(null);
                            }

                            if (lastPunch != null) {
                                dailyRecord.setActualPunchOutTime(lastPunch.getPunchTime().toLocalTime());
                                dailyRecord.setPunchOutDevice(lastPunch.getPunchDevice());
                            } else {
                                dailyRecord.setActualPunchOutTime(null);
                            }
                        }
                    }
                }
            } else {
                // 没有排班记录，视为休息日
                dailyRecord.setScheduledDurationHours(BigDecimal.ZERO);
                dailyRecord.setAttendanceStatus("休息日");
                dailyRecord.setActualWorkHours(BigDecimal.ZERO);
                dailyRecord.setIsLate(false);
                dailyRecord.setIsEarlyLeave(false);
                dailyRecord.setIsAbsent(false);
            }
            dailyRecords.add(dailyRecord);
        }

        // 5. 汇总月度数据
        summary.setWhiteShiftDays(whiteShiftDays);
        summary.setNightShiftDays(nightShiftDays);
        summary.setRestDays((int) dailyRecords.stream()
                .filter(record -> "休息日".equals(record.getAttendanceStatus()))
                .count());

        summary.setTotalScheduledHours(totalScheduledHours.setScale(2, RoundingMode.HALF_UP));
        summary.setTotalActualWorkHours(totalActualWorkHours.setScale(2, RoundingMode.HALF_UP));
        summary.setActualAttendanceDays(actualAttendanceDays);
        summary.setLateCount(lateCount);
        summary.setEarlyLeaveCount(earlyLeaveCount);

        if (!schedulings.isEmpty()) {
            summary.setEmployeeName(schedulings.get(0).getEmployeeName());
        }

        summary.setAbsentDays(Math.max(0, summary.getTotalScheduledDays() - summary.getActualAttendanceDays()));

        // 计算比率
        if (summary.getTotalScheduledHours().compareTo(BigDecimal.ZERO) > 0) {
            summary.setAttendanceHoursRatio(summary.getTotalActualWorkHours()
                    .divide(summary.getTotalScheduledHours(), 4, RoundingMode.HALF_UP));
        } else {
            summary.setAttendanceHoursRatio(BigDecimal.ZERO);
        }

        if (summary.getTotalScheduledDays() > 0) {
            summary.setAttendanceDaysRatio(BigDecimal.valueOf(summary.getActualAttendanceDays())
                    .divide(BigDecimal.valueOf(summary.getTotalScheduledDays()), 4, RoundingMode.HALF_UP));
        } else {
            summary.setAttendanceDaysRatio(BigDecimal.ZERO);
        }

        report.setDailyRecords(dailyRecords);
        report.setMonthlySummary(summary);

        return report;
    }

    /**
     * 辅助方法：根据排班日期和班次信息，在所有打卡记录中查找对应的最早和最晚打卡。
     * 该方法考虑了跨天班次的情况，并根据班次的有效打卡时间窗进行筛选。
     *
     * @param scheduleDate 当前的排班日期 (例如：2025-07-11)
     * @param shift 班次详情 (包含开始时间、结束时间、提前/延迟打卡分钟数)
     * @param allPunchRecordsMap 所有员工在整个月份及周边日期的打卡记录，按日期分组
     * @return 包含 "first" (最早打卡) 和 "last" (最晚打卡) 的Map，如果未找到则Map中的值为null
     */
    private Map<String, PunchRecord> findPunchesForShift(
            LocalDate scheduleDate, Shift shift, Map<LocalDate, List<PunchRecord>> allPunchRecordsMap) {

        if (shift.getStartTime() == null || shift.getEndTime() == null) {
            return new HashMap<>();
        }

        // 1. 计算完整的有效打卡时间窗
        LocalTime shiftStartTime = shift.getStartTime();
        LocalTime shiftEndTime = shift.getEndTime();
        int earlyPunchInMargin = shift.getEarlyPunchInMinutes() != null ? shift.getEarlyPunchInMinutes() : EARLY_PUNCH_IN_MARGIN_MINUTES;
        int latePunchOutMargin = shift.getLatePunchOutMinutes() != null ? shift.getLatePunchOutMinutes() : LATE_PUNCH_OUT_MARGIN_MINUTES;

        LocalDateTime plannedShiftStartDateTime = LocalDateTime.of(scheduleDate, shiftStartTime);
        LocalDateTime windowStart = plannedShiftStartDateTime.minusMinutes(earlyPunchInMargin);

        LocalDateTime plannedShiftEndDateTime;
        if (shiftStartTime.isAfter(shiftEndTime)) {
            plannedShiftEndDateTime = LocalDateTime.of(scheduleDate.plusDays(1), shiftEndTime);
        } else {
            plannedShiftEndDateTime = LocalDateTime.of(scheduleDate, shiftEndTime);
        }
        LocalDateTime windowEnd = plannedShiftEndDateTime.plusMinutes(latePunchOutMargin);

        // 2. 收集这个大窗口内的所有相关打卡记录
        List<PunchRecord> allRelevantPunches = new ArrayList<>();
        for (LocalDate date = windowStart.toLocalDate(); !date.isAfter(windowEnd.toLocalDate()); date = date.plusDays(1)) {
            List<PunchRecord> recordsOnDate = allPunchRecordsMap.getOrDefault(date, new ArrayList<>());
            for (PunchRecord record : recordsOnDate) {
                if (!record.getPunchTime().isBefore(windowStart) && !record.getPunchTime().isAfter(windowEnd)) {
                    allRelevantPunches.add(record);
                }
            }
        }

        // 如果总共都没有打卡记录，直接返回
        if (allRelevantPunches.isEmpty()) {
            return new HashMap<>();
        }

        // 3. 计算班次中点，用于分割上下班打卡范围
        Duration shiftDuration = Duration.between(plannedShiftStartDateTime, plannedShiftEndDateTime);
        LocalDateTime shiftMidpoint = plannedShiftStartDateTime.plus(shiftDuration.dividedBy(2));

        // 4. 在上班时间范围 (从窗口开始到班次中点) 内查找最早的打卡记录
        PunchRecord firstPunch = allRelevantPunches.stream()
                .filter(p -> !p.getPunchTime().isAfter(shiftMidpoint)) // 只考虑中点之前的打卡
                .min(Comparator.comparing(PunchRecord::getPunchTime))
                .orElse(null);

        // 5. 在下班时间范围 (从班次中点到窗口结束) 内查找最晚的打卡记录
        PunchRecord lastPunch = allRelevantPunches.stream()
                .filter(p -> p.getPunchTime().isAfter(shiftMidpoint)) // 只考虑中点之后的打卡
                .max(Comparator.comparing(PunchRecord::getPunchTime))
                .orElse(null);


        Map<String, PunchRecord> result = new HashMap<>();
        result.put("first", firstPunch);
        result.put("last", lastPunch);
        return result;
    }

    @Override
    public Page<AttendanceDailyRecordDTO> getDailyAttendanceRecords(
            LocalDate startDate, LocalDate endDate, String teamName, String employeeName, long pageNum, long pageSize) {

        // 1. 构建查询条件，获取时间范围内所有相关排班记录 - 使用数据库分页
        QueryWrapper<Scheduling> schedulingQueryWrapper = new QueryWrapper<>();
        schedulingQueryWrapper.between("schedule_date", startDate, endDate);
        if (teamName != null && !teamName.isEmpty()) {
            schedulingQueryWrapper.like("team_name", teamName);
        }
        if (employeeName != null && !employeeName.isEmpty()) {
            schedulingQueryWrapper.like("employee_name", employeeName);
        }
        schedulingQueryWrapper.orderByAsc("schedule_date", "employee_number");

        // 2. 直接使用数据库分页查询排班记录
        Page<Scheduling> schedulingPage = new Page<>(pageNum, pageSize);
        Page<Scheduling> schedulingResult = schedulingService.page(schedulingPage, schedulingQueryWrapper);

        // 如果没有找到符合条件的排班，直接返回空分页结果
        if (schedulingResult.getRecords().isEmpty()) {
            return new Page<>(pageNum, pageSize, 0);
        }

        // 3. 获取所有班次信息，转为Map
        Map<String, Shift> shiftMap = shiftService.list().stream()
                .collect(Collectors.toMap(Shift::getShiftCode, shift -> shift));

        // 4. 批量获取当前页所有员工的打卡记录，避免在循环中多次查询数据库
        List<String> employeeNumbers = schedulingResult.getRecords().stream()
                .map(Scheduling::getEmployeeNumber)
                .distinct()
                .collect(Collectors.toList());

        Map<String, List<PunchRecord>> employeePunchRecordsMap = batchGetEmployeePunchRecords(
                employeeNumbers, startDate.minusDays(1), endDate.plusDays(1));

        // 5. 处理当前页的排班记录，生成对应的日考勤记录
        List<AttendanceDailyRecordDTO> pagedDailyRecords = new ArrayList<>();

        for (Scheduling scheduling : schedulingResult.getRecords()) {
            AttendanceDailyRecordDTO dailyRecord = processSingleDayAttendanceForQueryOptimized(
                    scheduling, shiftMap, employeePunchRecordsMap);
            pagedDailyRecords.add(dailyRecord);
        }

        // 6. 构建分页结果
        Page<AttendanceDailyRecordDTO> resultPage = new Page<>(pageNum, pageSize, schedulingResult.getTotal());
        resultPage.setRecords(pagedDailyRecords);

        return resultPage;
    }

    /**
     * 批量获取多个员工的打卡记录
     */
    private Map<String, List<PunchRecord>> batchGetEmployeePunchRecords(
            List<String> employeeNumbers, LocalDate startDate, LocalDate endDate) {

        QueryWrapper<PunchRecord> queryWrapper = new QueryWrapper<>();
        queryWrapper.in("employee_number", employeeNumbers)
                .between("punch_time", startDate.atStartOfDay(), endDate.atTime(LocalTime.MAX))
                .eq("is_valid", true)
                .orderByAsc("punch_time");

        List<PunchRecord> allPunchRecords = punchRecordService.list(queryWrapper);

        return allPunchRecords.stream()
                .collect(Collectors.groupingBy(PunchRecord::getEmployeeNumber));
    }

    /**
     * 优化后的单天考勤处理方法，使用预查询的打卡记录
     */
    private AttendanceDailyRecordDTO processSingleDayAttendanceForQueryOptimized(
            Scheduling scheduling, Map<String, Shift> shiftMap, Map<String, List<PunchRecord>> employeePunchRecordsMap) {

        AttendanceDailyRecordDTO dailyRecord = new AttendanceDailyRecordDTO();

        // 设置基本信息
        LocalDate currentDate = scheduling.getScheduleDate();
        dailyRecord.setDate(currentDate);
        dailyRecord.setDayOfWeek(currentDate.getDayOfWeek().name());
        dailyRecord.setTeamName(scheduling.getTeamName());
        dailyRecord.setEmployeeName(scheduling.getEmployeeName());
        dailyRecord.setEmployeeNumber(scheduling.getEmployeeNumber());
        dailyRecord.setShiftCode(scheduling.getShiftCode());
        dailyRecord.setScheduledDurationHours(scheduling.getScheduledDurationHours() != null ? scheduling.getScheduledDurationHours() : BigDecimal.ZERO);

        // 从预查询的结果中获取该员工的打卡记录
        List<PunchRecord> employeePunchRecords = employeePunchRecordsMap.getOrDefault(scheduling.getEmployeeNumber(), new ArrayList<>());

        // 将打卡记录按自然日分组
        Map<LocalDate, List<PunchRecord>> punchRecordsMap = employeePunchRecords.stream()
                .collect(Collectors.groupingBy(punchRecord -> punchRecord.getPunchTime().toLocalDate()));

        // 获取班次信息
        Shift shift = shiftMap.get(scheduling.getShiftCode());

        if (shift != null) {
            dailyRecord.setShiftName(shift.getShiftName());
            dailyRecord.setScheduledStartTime(shift.getStartTime());
            dailyRecord.setScheduledEndTime(shift.getEndTime());

            // **核心逻辑：区分工作班次和休息班次**
            if ("R".equals(shift.getShiftCode())) {
                // 休息班次
                dailyRecord.setAttendanceStatus("休息日");
                dailyRecord.setActualWorkHours(BigDecimal.ZERO);
                dailyRecord.setIsLate(false);
                dailyRecord.setIsEarlyLeave(false);
                dailyRecord.setIsAbsent(false);
            } else if (shift.getStartTime() != null && shift.getEndTime() != null) {
                // 工作班次，使用现有逻辑查找打卡记录
                Map<String, PunchRecord> punches = findPunchesForShift(currentDate, shift, punchRecordsMap);
                PunchRecord firstPunch = punches.get("first");
                PunchRecord lastPunch = punches.get("last");

                if (firstPunch != null && lastPunch != null) {
                    // --- 情况1: 上下班打卡都齐全 ---
                    dailyRecord.setActualPunchInTime(firstPunch.getPunchTime().toLocalTime());
                    dailyRecord.setActualPunchOutTime(lastPunch.getPunchTime().toLocalTime());
                    dailyRecord.setPunchInDevice(firstPunch.getPunchDevice());
                    dailyRecord.setPunchOutDevice(lastPunch.getPunchDevice());

                    // 计算实际工作时长（小时）
                    Duration actualDuration = Duration.between(firstPunch.getPunchTime(), lastPunch.getPunchTime());
                    dailyRecord.setActualWorkHours(BigDecimal.valueOf(actualDuration.toMinutes()).divide(new BigDecimal("60"), 2, RoundingMode.HALF_UP));

                    // 判断是否迟到早退
                    boolean isLate = false;
                    boolean isEarlyLeave = false;

                    // 初始化迟到早退时长为0
                    dailyRecord.setLateDurationHours(BigDecimal.ZERO);
                    dailyRecord.setEarlyLeaveDurationHours(BigDecimal.ZERO);

                    // 计划上班时间戳 & 迟到判断线
                    LocalDateTime plannedShiftStartDateTime = LocalDateTime.of(currentDate, shift.getStartTime());
                    LocalDateTime lateToleranceTime = plannedShiftStartDateTime.plusMinutes(ATTENDANCE_GRACE_MINUTES);

                    if (firstPunch.getPunchTime().isAfter(lateToleranceTime)) {
                        isLate = true;
                        // 计算并设置迟到时长
                        Duration lateDuration = Duration.between(lateToleranceTime, firstPunch.getPunchTime());
                        dailyRecord.setLateDurationHours(BigDecimal.valueOf(lateDuration.toMinutes()).divide(new BigDecimal("60"), 2, RoundingMode.HALF_UP));
                    }

                    // 计划下班时间戳 & 早退判断线
                    LocalDateTime plannedShiftEndDateTime;
                    if (shift.getStartTime().isAfter(shift.getEndTime())) { // 跨天班次
                        plannedShiftEndDateTime = LocalDateTime.of(currentDate.plusDays(1), shift.getEndTime());
                    } else { // 非跨天班次
                        plannedShiftEndDateTime = LocalDateTime.of(currentDate, shift.getEndTime());
                    }
                    LocalDateTime earlyLeaveToleranceTime = plannedShiftEndDateTime.minusMinutes(ATTENDANCE_GRACE_MINUTES);

                    if (lastPunch.getPunchTime().isBefore(earlyLeaveToleranceTime)) {
                        isEarlyLeave = true;
                        // 计算并设置早退时长
                        Duration earlyLeaveDuration = Duration.between(lastPunch.getPunchTime(), earlyLeaveToleranceTime);
                        dailyRecord.setEarlyLeaveDurationHours(BigDecimal.valueOf(earlyLeaveDuration.toMinutes()).divide(new BigDecimal("60"), 2, RoundingMode.HALF_UP));
                    }

                    dailyRecord.setIsLate(isLate);
                    dailyRecord.setIsEarlyLeave(isEarlyLeave);
                    dailyRecord.setIsAbsent(false); // 不缺勤

                    // 综合判断考勤状态
                    if (isLate && isEarlyLeave) {
                        dailyRecord.setAttendanceStatus("迟到早退");
                    } else if (isLate) {
                        dailyRecord.setAttendanceStatus("迟到");
                    } else if (isEarlyLeave) {
                        dailyRecord.setAttendanceStatus("早退");
                    } else {
                        dailyRecord.setAttendanceStatus("正常");
                    }

                } else {
                    // 1. 统一将最终结果设置为“缺勤”，并将所有计算值重置
                    dailyRecord.setAttendanceStatus("缺勤");
                    dailyRecord.setIsAbsent(true);
                    dailyRecord.setActualWorkHours(BigDecimal.ZERO);
                    dailyRecord.setIsLate(false);
                    dailyRecord.setIsEarlyLeave(false);
                    dailyRecord.setLateDurationHours(BigDecimal.ZERO);
                    dailyRecord.setEarlyLeaveDurationHours(BigDecimal.ZERO);

                    // 2. 保留实际发生的打卡记录和设备信息
                    if (firstPunch != null) {
                        dailyRecord.setActualPunchInTime(firstPunch.getPunchTime().toLocalTime());
                        dailyRecord.setPunchInDevice(firstPunch.getPunchDevice());
                    } else {
                        dailyRecord.setActualPunchInTime(null);
                        dailyRecord.setPunchInDevice(null);
                    }

                    if (lastPunch != null) {
                        dailyRecord.setActualPunchOutTime(lastPunch.getPunchTime().toLocalTime());
                        dailyRecord.setPunchOutDevice(lastPunch.getPunchDevice());
                    } else {
                        dailyRecord.setActualPunchOutTime(null);
                        dailyRecord.setPunchOutDevice(null);
                    }
                }
            }
        }
        return dailyRecord;
    }
    @Override
    public Page<MonthlyAttendanceSummaryByTeamDTO> getMonthlyAttendanceSummaryByTeam(
            YearMonth yearMonth, String teamName, long pageNum, long pageSize) {

        LocalDate startDate = yearMonth.atDay(1);
        LocalDate endDate = yearMonth.atEndOfMonth();

        // 1. 构建查询条件，获取符合条件的单位列表并分页
        QueryWrapper<Scheduling> teamQueryWrapper = new QueryWrapper<>();
        teamQueryWrapper.select("DISTINCT team_name")
                .between("schedule_date", startDate, endDate);
        if (teamName != null && !teamName.isEmpty()) {
            teamQueryWrapper.like("team_name", teamName);
        }
        teamQueryWrapper.orderByAsc("team_name");

        // 2. 使用数据库分页查询单位
        Page<Scheduling> teamPage = new Page<>(pageNum, pageSize);
        Page<Scheduling> pagedTeamSchedulings = schedulingService.page(teamPage, teamQueryWrapper);

        // 如果没有找到符合条件的单位，直接返回空分页结果
        if (pagedTeamSchedulings.getRecords().isEmpty()) {
            return new Page<>(pageNum, pageSize, 0);
        }

        // 3. 提取当前页的单位名称
        List<String> pagedTeamNames = pagedTeamSchedulings.getRecords().stream()
                .map(Scheduling::getTeamName)
                .distinct()
                .collect(Collectors.toList());

        // 4. 为每个单位生成汇总统计
        List<MonthlyAttendanceSummaryByTeamDTO> summaryList = new ArrayList<>();
        for (int i = 0; i < pagedTeamNames.size(); i++) {
            String team = pagedTeamNames.get(i);
            MonthlyAttendanceSummaryByTeamDTO summary = calculateTeamAttendanceSummary(
                    team, yearMonth, (int) ((pageNum - 1) * pageSize + i + 1));
            summaryList.add(summary);
        }

        // 5. 构建并返回分页结果
        Page<MonthlyAttendanceSummaryByTeamDTO> resultPage = new Page<>(pageNum, pageSize, pagedTeamSchedulings.getTotal());
        resultPage.setRecords(summaryList);

        return resultPage;
    }

    /**
     * 计算单个单位的考勤汇总
     */
    private MonthlyAttendanceSummaryByTeamDTO calculateTeamAttendanceSummary(
            String teamName, YearMonth yearMonth, int sequenceNumber) {

        MonthlyAttendanceSummaryByTeamDTO summary = new MonthlyAttendanceSummaryByTeamDTO();
        summary.setSequenceNumber(sequenceNumber);
        summary.setTeamName(teamName);

        LocalDate startDate = yearMonth.atDay(1);
        LocalDate endDate = yearMonth.atEndOfMonth();

        // 1. 获取该单位的所有员工
        QueryWrapper<Scheduling> employeeQueryWrapper = new QueryWrapper<>();
        employeeQueryWrapper.select("DISTINCT employee_number")
                .eq("team_name", teamName)
                .between("schedule_date", startDate, endDate);

        List<Scheduling> employeeSchedulings = schedulingService.list(employeeQueryWrapper);
        List<String> employeeNumbers = employeeSchedulings.stream()
                .map(Scheduling::getEmployeeNumber)
                .collect(Collectors.toList());

        if (employeeNumbers.isEmpty()) {
            // 如果没有员工，返回空统计
            summary.setTotalEmployees(0);
            summary.setAbsentOver10Days(0);
            summary.setAbsent5To10Days(0);
            summary.setAbsent1To5Days(0);
            summary.setWhiteShiftDays(0);
            summary.setNightShiftDays(0);
            return summary;
        }

        // 2. 复用现有的批量获取员工数据的方法
        BatchEmployeeDataDTO batchData = batchGetEmployeeData(employeeNumbers, yearMonth);

        // 3. 统计变量
        int totalEmployees = employeeNumbers.size();
        int absentOver10Days = 0;
        int absent5To10Days = 0;
        int absent1To5Days = 0;
        int totalWhiteShiftDays = 0;
        int totalNightShiftDays = 0;

        // 4. 遍历每个员工，复用现有的员工考勤报告生成方法
        for (String employeeNumber : employeeNumbers) {
            // 复用现有方法获取员工的月度考勤报告
            AttendanceMonthlyReportDTO employeeReport = getMonthlyAttendanceReportForSingleEmployeeOptimized(
                    employeeNumber, yearMonth, batchData);

            AttendanceMonthlySummaryDTO employeeSummary = employeeReport.getMonthlySummary();

            // 统计缺勤天数分类
            int absentDays = employeeSummary.getAbsentDays();
            if (absentDays >= 10) {
                absentOver10Days++;
            } else if (absentDays >= 5) {
                absent5To10Days++;
            } else if (absentDays >= 1) {
                absent1To5Days++;
            }

            // 累计班次天数
            totalWhiteShiftDays += employeeSummary.getWhiteShiftDays();
            totalNightShiftDays += employeeSummary.getNightShiftDays();
        }

        // 5. 设置统计结果
        summary.setTotalEmployees(totalEmployees);
        summary.setAbsentOver10Days(absentOver10Days);
        summary.setAbsent5To10Days(absent5To10Days);
        summary.setAbsent1To5Days(absent1To5Days);
        summary.setWhiteShiftDays(totalWhiteShiftDays);
        summary.setNightShiftDays(totalNightShiftDays);

        return summary;
    }

    @Override
    public void exportMonthlyAttendanceReport(
            YearMonth yearMonth, String teamName, String employeeName,
            HttpServletResponse response) throws IOException {

        // 1. 获取所有数据（不分页）
        List<AttendanceMonthlyReportDTO> allReports = getAllMonthlyAttendanceReports(yearMonth, teamName, employeeName);

        // 2. 转换为带说明列的导出数据
        List<List<Object>> exportData = convertToExportDataWithDescription(allReports, yearMonth);

        // 3. 设置响应头
        response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
        response.setCharacterEncoding("utf-8");
        String fileName = yearMonth.getYear() + "年" + yearMonth.getMonthValue() + "月考勤报表";
        response.setHeader("Content-disposition", "attachment;filename*=utf-8''" +
                URLEncoder.encode(fileName, "UTF-8") + ".xlsx");

        // 4. 写入Excel
        EasyExcel.write(response.getOutputStream())
                .registerWriteHandler(new MonthlyAttendanceExcelHandler(yearMonth))
                .sheet(fileName)
                .relativeHeadRowIndex(2)
                .doWrite(exportData);
    }

    /**
     * 转换为带说明列的导出数据
     */
    private List<List<Object>> convertToExportDataWithDescription(List<AttendanceMonthlyReportDTO> reports, YearMonth yearMonth) {
        List<List<Object>> result = new ArrayList<>();
        int daysInMonth = yearMonth.lengthOfMonth();

        for (int i = 0; i < reports.size(); i++) {
            AttendanceMonthlyReportDTO report = reports.get(i);
            AttendanceMonthlySummaryDTO summary = report.getMonthlySummary();

            // 构建日期数据映射
            Map<Integer, AttendanceDailyRecordDTO> dailyMap = report.getDailyRecords().stream()
                    .collect(Collectors.toMap(
                            record -> record.getDate().getDayOfMonth(),
                            record -> record
                    ));

            // 说明列的内容
            String[] descriptionLabels = {"排班班次", "排班时长", "上班时长", "是否正常"};

            // 每个员工生成4行数据
            for (int rowType = 0; rowType < 4; rowType++) {
                List<Object> row = new ArrayList<>();

                // 固定列处理
                if (rowType == 0) {
                    // 第1行：基本信息
                    row.add(i + 1);
                    row.add(summary.getEmployeeName());
                    row.add(summary.getEmployeeNumber());

                    String teamName = "";
                    if (!report.getDailyRecords().isEmpty()) {
                        teamName = report.getDailyRecords().get(0).getTeamName();
                    }
                    row.add(teamName);
                } else {
                    // 第2-4行：固定列留空
                    row.add("");
                    row.add("");
                    row.add("");
                    row.add("");
                }

                // 说明列
                row.add(descriptionLabels[rowType]);

                // 日期列处理（固定31列）
                for (int day = 1; day <= 31; day++) {
                    if (day <= daysInMonth) {
                        AttendanceDailyRecordDTO dailyRecord = dailyMap.get(day);
                        if (dailyRecord != null) {
                            switch (rowType) {
                                case 0: // 第1行：班次
                                    row.add(dailyRecord.getShiftCode() != null ? dailyRecord.getShiftCode() : "");
                                    break;
                                case 1: // 第2行：排班时长
                                    row.add(dailyRecord.getScheduledDurationHours() != null ?
                                            dailyRecord.getScheduledDurationHours().toString() : "0");
                                    break;
                                case 2: // 第3行：实际时长
                                    row.add(dailyRecord.getActualWorkHours() != null ?
                                            dailyRecord.getActualWorkHours().toString() : "0");
                                    break;
                                case 3: // 第4行：状态
                                    row.add(dailyRecord.getAttendanceStatus() != null ?
                                            dailyRecord.getAttendanceStatus() : "");
                                    break;
                            }
                        } else {
                            row.add("");
                        }
                    } else {
                        row.add("");
                    }
                }

                // 汇总列处理（固定10列）
                if (rowType == 0) {
                    // 第1行：显示汇总数据
                    row.add(summary.getWhiteShiftDays());
                    row.add(summary.getNightShiftDays());
                    row.add(summary.getTotalScheduledHours());
                    row.add(summary.getTotalActualWorkHours());
                    row.add(summary.getTotalScheduledDays());
                    row.add(summary.getActualAttendanceDays());
                    row.add(summary.getAbsentDays());
                    row.add(summary.getLateCount() + summary.getEarlyLeaveCount());
                    row.add(summary.getAttendanceHoursRatio() != null ?
                            summary.getAttendanceHoursRatio().multiply(new BigDecimal(100)).setScale(2, RoundingMode.HALF_UP) + "%" : "0%");
                    row.add(summary.getAttendanceDaysRatio() != null ?
                            summary.getAttendanceDaysRatio().multiply(new BigDecimal(100)).setScale(2, RoundingMode.HALF_UP) + "%" : "0%");
                } else {
                    // 第2-4行：汇总列留空
                    for (int j = 0; j < 10; j++) {
                        row.add("");
                    }
                }

                result.add(row);
            }
        }

        return result;
    }



    /**
     * 获取所有月度考勤报告（不分页）
     */
    private List<AttendanceMonthlyReportDTO> getAllMonthlyAttendanceReports(
            YearMonth yearMonth, String teamName, String employeeName) {

        // 复用现有查询逻辑，但不分页
        LocalDate queryMonthStart = yearMonth.atDay(1);
        LocalDate queryMonthEnd = yearMonth.atEndOfMonth();

        QueryWrapper<Scheduling> employeeQueryWrapper = new QueryWrapper<>();
        employeeQueryWrapper.select("DISTINCT employee_number")
                .between("schedule_date", queryMonthStart, queryMonthEnd);
        if (teamName != null && !teamName.isEmpty()) {
            employeeQueryWrapper.like("team_name", teamName);
        }
        if (employeeName != null && !employeeName.isEmpty()) {
            employeeQueryWrapper.like("employee_name", employeeName);
        }
        employeeQueryWrapper.orderByAsc("employee_number");

        List<Scheduling> schedulings = schedulingService.list(employeeQueryWrapper);
        List<String> employeeNumbers = schedulings.stream()
                .map(Scheduling::getEmployeeNumber)
                .collect(Collectors.toList());

        if (employeeNumbers.isEmpty()) {
            return new ArrayList<>();
        }

        // 批量获取所有员工的基础数据
        BatchEmployeeDataDTO batchData = batchGetEmployeeData(employeeNumbers, yearMonth);

        // 为每个员工生成月度考勤报告
        List<AttendanceMonthlyReportDTO> reports = new ArrayList<>();
        for (String empNumber : employeeNumbers) {
            AttendanceMonthlyReportDTO report = getMonthlyAttendanceReportForSingleEmployeeOptimized(
                    empNumber, yearMonth, batchData);
            reports.add(report);
        }

        return reports;
    }

    /**
     * 导出月度考勤汇总报表
     */

    /**
     * 导出月度考勤汇总报表
     */
    @Override
    public void exportMonthlySummaryReport(
            YearMonth yearMonth, String teamName, String employeeName,
            HttpServletResponse response) throws IOException {

        // 1. 获取月度考勤报表数据
        List<AttendanceMonthlyReportDTO> allReports = getAllMonthlyAttendanceReports(yearMonth, teamName, employeeName);

        // 2. 转换为汇总数据
        List<MonthlyAttendanceSummaryByTeamDTO> summaryData = convertToTeamSummaryData(allReports);

        // 3. 转换为Excel导出格式
        List<List<Object>> exportData = convertTeamSummaryToExportData(summaryData);

        // 4. 设置响应头
        response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
        response.setCharacterEncoding("utf-8");
        String fileName = yearMonth.getYear() + "年" + yearMonth.getMonthValue() + "月考勤汇总";
        response.setHeader("Content-disposition", "attachment;filename*=utf-8''" +
                URLEncoder.encode(fileName, "UTF-8") + ".xlsx");

        // 5. 写入Excel，数据从第3行开始（行索引为2）
        EasyExcel.write(response.getOutputStream())
                .registerWriteHandler(new MonthlyAttendanceSummaryExcelHandler())
                .sheet("月度考勤汇总")
                .relativeHeadRowIndex(2) // 关键修改：数据从第3行开始写入
                .doWrite(exportData);
    }


    /**
     * 将月度报表数据转换为按班组汇总的数据
     */
    private List<MonthlyAttendanceSummaryByTeamDTO> convertToTeamSummaryData(List<AttendanceMonthlyReportDTO> reports) {
        // 按班组分组统计
        Map<String, List<AttendanceMonthlyReportDTO>> teamGroups = reports.stream()
                .collect(Collectors.groupingBy(report -> {
                    if (!report.getDailyRecords().isEmpty()) {
                        return report.getDailyRecords().get(0).getTeamName();
                    }
                    return "未知班组";
                }));

        List<MonthlyAttendanceSummaryByTeamDTO> summaryList = new ArrayList<>();
        int sequenceNumber = 1;

        for (Map.Entry<String, List<AttendanceMonthlyReportDTO>> entry : teamGroups.entrySet()) {
            String teamName = entry.getKey();
            List<AttendanceMonthlyReportDTO> teamReports = entry.getValue();

            MonthlyAttendanceSummaryByTeamDTO summary = new MonthlyAttendanceSummaryByTeamDTO();
            summary.setSequenceNumber(sequenceNumber++);
            summary.setTeamName(teamName);
            summary.setTotalEmployees(teamReports.size());

            // 统计缺勤人数
            int absentOver10 = 0;
            int absent5To10 = 0;
            int absent1To5 = 0;
            int totalWhiteShift = 0;
            int totalNightShift = 0;

            for (AttendanceMonthlyReportDTO report : teamReports) {
                AttendanceMonthlySummaryDTO monthlySummary = report.getMonthlySummary();

                // 统计缺勤天数
                Integer absentDays = monthlySummary.getAbsentDays();
                if (absentDays != null) {
                    if (absentDays >= 10) {
                        absentOver10++;
                    } else if (absentDays >= 5) {
                        absent5To10++;
                    } else if (absentDays >= 1) {
                        absent1To5++;
                    }
                }

                // 统计班次
                totalWhiteShift += monthlySummary.getWhiteShiftDays();
                totalNightShift += monthlySummary.getNightShiftDays();
            }

            summary.setAbsentOver10Days(absentOver10);
            summary.setAbsent5To10Days(absent5To10);
            summary.setAbsent1To5Days(absent1To5);
            summary.setWhiteShiftDays(totalWhiteShift);
            summary.setNightShiftDays(totalNightShift);

            summaryList.add(summary);
        }

        return summaryList;
    }

    /**
     * 将汇总数据转换为Excel导出格式
     */
    private List<List<Object>> convertTeamSummaryToExportData(List<MonthlyAttendanceSummaryByTeamDTO> summaryData) {
        List<List<Object>> result = new ArrayList<>();

        for (MonthlyAttendanceSummaryByTeamDTO summary : summaryData) {
            List<Object> row = new ArrayList<>();

            row.add(summary.getSequenceNumber());
            row.add(summary.getTeamName());
            row.add(summary.getTotalEmployees());
            row.add(summary.getAbsentOver10Days());
            row.add(summary.getAbsent5To10Days());
            row.add(summary.getAbsent1To5Days());
            row.add(summary.getWhiteShiftDays());
            row.add(summary.getNightShiftDays());

            result.add(row);
        }

        return result;
    }
    /**
     * 新增方法：获取所有日考勤记录（不分页），用于导出
     */
    private List<AttendanceDailyRecordDTO> getAllDailyAttendanceRecords(
            LocalDate startDate, LocalDate endDate, String teamName, String employeeName) {
        // 1. 构建查询条件，获取时间范围内所有相关排班记录
        QueryWrapper<Scheduling> schedulingQueryWrapper = new QueryWrapper<>();
        schedulingQueryWrapper.between("schedule_date", startDate, endDate);
        if (teamName != null && !teamName.isEmpty()) {
            schedulingQueryWrapper.like("team_name", teamName);
        }
        if (employeeName != null && !employeeName.isEmpty()) {
            schedulingQueryWrapper.like("employee_name", employeeName);
        }
        schedulingQueryWrapper.orderByAsc("schedule_date", "employee_number");
        // 2. 查询所有符合条件的排班记录
        List<Scheduling> allSchedulings = schedulingService.list(schedulingQueryWrapper);
        if (allSchedulings.isEmpty()) {
            return new ArrayList<>();
        }
        // 3. 获取所有班次信息
        Map<String, Shift> shiftMap = shiftService.list().stream()
                .collect(Collectors.toMap(Shift::getShiftCode, shift -> shift));
        // 4. 批量获取所有相关员工的打卡记录
        List<String> employeeNumbers = allSchedulings.stream()
                .map(Scheduling::getEmployeeNumber)
                .distinct()
                .collect(Collectors.toList());
        Map<String, List<PunchRecord>> employeePunchRecordsMap = batchGetEmployeePunchRecords(
                employeeNumbers, startDate.minusDays(1), endDate.plusDays(1));
        // 5. 处理所有排班记录，生成对应的日考勤记录列表
        List<AttendanceDailyRecordDTO> allDailyRecords = new ArrayList<>();
        for (Scheduling scheduling : allSchedulings) {
            AttendanceDailyRecordDTO dailyRecord = processSingleDayAttendanceForQueryOptimized(
                    scheduling, shiftMap, employeePunchRecordsMap);
            allDailyRecords.add(dailyRecord);
        }

        return allDailyRecords;
    }
    /**
     * 新增方法：实现日考勤记录的导出逻辑
     */
    @Override
    public void exportDailyAttendanceRecords(
            LocalDate startDate, LocalDate endDate, String teamName, String employeeName, HttpServletResponse response) throws IOException {
        // 1. 获取所有需要导出的数据
        List<AttendanceDailyRecordDTO> dailyRecords = getAllDailyAttendanceRecords(startDate, endDate, teamName, employeeName);

        // 2. 将数据转换为导出专用的 DTO 列表
        List<DailyAttendanceExportDTO> exportList = new ArrayList<>();
        for (int i = 0; i < dailyRecords.size(); i++) {
            AttendanceDailyRecordDTO record = dailyRecords.get(i);
            DailyAttendanceExportDTO exportDTO = new DailyAttendanceExportDTO();
            exportDTO.setSequenceNumber(i + 1);
            exportDTO.setTeamName(record.getTeamName());
            exportDTO.setEmployeeNumber(record.getEmployeeNumber());
            exportDTO.setEmployeeName(record.getEmployeeName());
            exportDTO.setDate(record.getDate());
            exportDTO.setActualPunchInTime(record.getActualPunchInTime());
            exportDTO.setActualPunchOutTime(record.getActualPunchOutTime());
            exportDTO.setPunchInDevice(record.getPunchInDevice());
            exportDTO.setPunchOutDevice(record.getPunchOutDevice());
            exportDTO.setShiftName(record.getShiftName());
            exportDTO.setAttendanceStatus(record.getAttendanceStatus());
            exportDTO.setLateDurationHours(record.getLateDurationHours());
            exportDTO.setEarlyLeaveDurationHours(record.getEarlyLeaveDurationHours());
            exportDTO.setActualWorkHours(record.getActualWorkHours());

            exportList.add(exportDTO);
        }


        // 3. 设置 HTTP 响应头
        response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
        response.setCharacterEncoding("utf-8");
        String fileName = String.format("日考勤记录(%s至%s)",
                startDate.format(DateTimeFormatter.ISO_LOCAL_DATE),
                endDate.format(DateTimeFormatter.ISO_LOCAL_DATE));
        response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + URLEncoder.encode(fileName, "UTF-8") + ".xlsx");
        // 4. 使用 EasyExcel 写入数据到响应流
        EasyExcel.write(response.getOutputStream(), DailyAttendanceExportDTO.class)
                .sheet("日考勤记录")
                .doWrite(exportList);
    }
}